﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客-YGB.Grushy-文章分类-Linux驱动程序</title><link>http://www.cnitblog.com/ygb/category/2101.html</link><description /><language>zh-cn</language><lastBuildDate>Sat, 01 Oct 2011 14:22:41 GMT</lastBuildDate><pubDate>Sat, 01 Oct 2011 14:22:41 GMT</pubDate><ttl>60</ttl><item><title>如何编写Linux设备驱动程序</title><link>http://www.cnitblog.com/ygb/articles/7462.html</link><dc:creator>YGB.Grushy</dc:creator><author>YGB.Grushy</author><pubDate>Fri, 10 Mar 2006 07:14:00 GMT</pubDate><guid>http://www.cnitblog.com/ygb/articles/7462.html</guid><wfw:comment>http://www.cnitblog.com/ygb/comments/7462.html</wfw:comment><comments>http://www.cnitblog.com/ygb/articles/7462.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/ygb/comments/commentRss/7462.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/ygb/services/trackbacks/7462.html</trackback:ping><description><![CDATA[　序言 
<P>　　Linux是Unix操作系统的一种变种，在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统，但它dos或window环境下的驱动程序有很大的区别。在Linux环境下设计驱动程序，思想简洁，操作方便，功能也很强大，但是支持函数少，只能依赖kernel中的函数，有些常用的操作要自己来编写，而且调试也不方便。本人这几周来为实验室自行研制的一块多媒体卡编制了驱动程序，获得了一些经验，愿与Linux fans共享，有不当之处，请予指正。</P>
<P>　　以下的一些文字主要来源于khg，johnsonm的Write linux device driver，Brennan's Guide to Inline Assembly，The Linux A-Z，还有清华BBS上的有关device driver的一些资料. 这些资料有的已经过时，有的还有一些错误，我依据自己的试验结果进行了修正. </P>
<P>　　一、Linux device driver 的概念 </P>
<P>　　系统调用是操作系统内核和应用程序之间的接口，设备驱动程序是操作系统内核和机器硬件之间的接口.设备驱动程序为应用程序屏蔽了硬件的细节，这样在应用程序看来，硬件设备只是一个设备文件， 应用程序可以象操作普通文件一样对硬件设备进行操作.设备驱动程序是内核的一部分，它完成以下的功能: </P>
<P>　　1.对设备初始化和释放. </P>
<P>　　2.把数据从内核传送到硬件和从硬件读取数据. </P>
<P>　　3.读取应用程序传送给设备文件的数据和回送应用程序请求的数据. </P>
<P>　　4.检测和处理设备出现的错误. </P>
<P>　　在Linux操作系统下有两类主要的设备文件类型，一种是字符设备，另一种是块设备.字符设备和块设备的主要区别是:在对字符设备发出读/写请求时，实际的硬件I/O一般就紧接着发生了，块设备则不然，它利用一块系统内存作缓冲区，当用户进程对设备请求能满足用户的要求，就返回请求的数据，如果不能，就调用请求函数来进行实际的I/O操作.块设备是主要针对磁盘等慢速设备设计的，以免耗费过多的CPU时间来等待. </P>
<P>　　已经提到，用户进程是通过设备文件来与实际的硬件打交道.每个设备文件都都有其文件属性(c/b)，表示是字符设备还蔤强樯璞?另外每个文件都有两个设备号，第一个是主设备号，标识驱动程序，第二个是从设备号，标识使用同一个设备驱动程序的不同的硬件设备，比如有两个软盘，就可以用从设备号来区分他们.设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致，否则用户进程将无法访问到驱动程序. </P>
<P>　　最后必须提到的是，在用户进程调用驱动程序时，系统进入核心态，这时不再是抢先式调度.也就是说，系统必须在你的驱动程序的子函数返回后才能进行其他的工作.如果你的驱动程序陷入死循环，不幸的是你只有重新启动机器了，然后就是漫长的fsck.//hehe </P>
<P>　　读/写时，它首先察看缓冲区的内容，如果缓冲区的数据 </P>
<P>　　如何编写Linux操作系统下的设备驱动程序 </P>
<P><BR>　　二、实例剖析 </P>
<P>　　我们来写一个最简单的字符设备驱动程序。虽然它什么也不做，但是通过它可以了解Linux的设备驱动程序的工作原理.把下面的C代码输入机器，你就会获得一个真正的设备驱动程序.不过我的kernel是2.0.34，在低版本的kernel上可能会出现问题，我还没测试过.//xixi </P>
<P>　　#define __NO_VERSION__ <BR>　　#include &lt;linux/modules.h&gt; <BR>　　#include &lt;linux/version.h&gt; </P>
<P>　　char kernel_version [] = UTS_RELEASE; </P>
<P>　　这一段定义了一些版本信息，虽然用处不是很大，但也必不可少.Johnsonm说所有的驱动程序的开头都要包含&lt;linux/config.h&gt;，但我看倒是未必. </P>
<P>　　由于用户进程是通过设备文件同硬件打交道，对设备文件的操作方式不外乎就是一些系统调用，如 open，read，write，close....， 注意，不是fopen， fread，但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据结构: </P>
<P>struct file_operations { </P>
<P>int (*seek) (struct inode * ，struct file *， off_t ，int); <BR>int (*read) (struct inode * ，struct file *， char ，int); <BR>int (*write) (struct inode * ，struct file *， off_t ，int); <BR>int (*readdir) (struct inode * ，struct file *， struct dirent * ，int); <BR>int (*select) (struct inode * ，struct file *， int ，select_table *); <BR>int (*ioctl) (struct inode * ，struct file *， unsined int ，unsigned long); <BR>int (*mmap) (struct inode * ，struct file *， struct vm_area_struct *); <BR>int (*open) (struct inode * ，struct file *); <BR>int (*release) (struct inode * ，struct file *); <BR>int (*fsync) (struct inode * ，struct file *); <BR>int (*fasync) (struct inode * ，struct file *，int); <BR>int (*check_media_change) (struct inode * ，struct file *); <BR>int (*revalidate) (dev_t dev); <BR>} <BR>&nbsp;</P>
<P>　　这个结构的每一个成员的名字都对应着一个系统调用.用户进程利用系统调用在对设备文件进行诸如read/write操作时，系统调用通过设备文件的主设备号找到相应的设备驱动程序，然后读取这个数据结构相应的函数指针，接着把控制权交给该函数.这是linux的设备驱动程序工作的基本原理.既然是这样，则编写设备驱动程序的主要工作就是编写子函数，并填充file_operations的各个域. </P>
<P>　　相当简单，不是吗? </P>
<P>　　下面就开始写子程序. </P>
<P>#include &lt;linux/types.h&gt; <BR>#include &lt;linux/fs.h&gt; <BR>#include &lt;linux/mm.h&gt; <BR>#include &lt;linux/errno.h&gt; <BR>#include &lt;asm/segment.h&gt; <BR>unsigned int test_major = 0; </P>
<P>static int read_test(struct inode *node，struct file *file， <BR>char *buf，int count) <BR>{ </P>
<P>int left; </P>
<P>if (verify_area(VERIFY_WRITE，buf，count) == -EFAULT ) <BR>return -EFAULT; </P>
<P>for(left = count ; left &gt; 0 ; left--) <BR>{ <BR>__put_user(1，buf，1); <BR>buf++; <BR>} <BR>return count; <BR>} <BR>&nbsp;</P>
<P>　　这个函数是为read调用准备的.当调用read时，read_test()被调用，它把用户的缓冲区全部写1.buf 是read调用的一个参数.它是用户进程空间的一个地址.但是在read_test被调用时，系统进入核心态.所以不能使用buf这个地址，必须用__put_user()，这是kernel提供的一个函数，用于向用户传送数据.另外还有很多类似功能的函数.请参考.在向用户空间拷贝数据之前，必须验证buf是否可用。</P>
<P><BR>　　这就用到函数verify_area. </P>
<P>static int write_tibet(struct inode *inode，struct file *file， <BR>const char *buf，int count) <BR>{ <BR>return count; <BR>} </P>
<P>static int open_tibet(struct inode *inode，struct file *file ) <BR>{ <BR>MOD_INC_USE_COUNT; <BR>return 0; <BR>} </P>
<P>static void release_tibet(struct inode *inode，struct file *file ) <BR>{ <BR>MOD_DEC_USE_COUNT; <BR>} <BR>&nbsp;</P>
<P>　　这几个函数都是空操作.实际调用发生时什么也不做，他们仅仅为下面的结构提供函数指针。 </P>
<P>struct file_operations test_fops = { <BR>NULL， <BR>read_test， <BR>write_test， <BR>NULL， /* test_readdir */ <BR>NULL， <BR>NULL， /* test_ioctl */ <BR>NULL， /* test_mmap */ <BR>open_test， <BR>release_test， NULL， /* test_fsync */ <BR>NULL， /* test_fasync */ <BR>/* nothing more， fill with NULLs */ <BR>};&nbsp; </P>
<P>　　设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序可以按照两种方式编译。一种是编译进kernel，另一种是编译成模块(modules)，如果编译进内核的话，会增加内核的大小，还要改动内核的源文件，而且不能动态的卸载，不利于调试，所以推荐使用模块方式。 </P>
<P>int init_module(void) <BR>{ <BR>int result; </P>
<P>result = register_chrdev(0， "test"， &amp;test_fops); </P>
<P>if (result &lt; 0) { <BR>printk(KERN_INFO "test: can't get major number\n"); <BR>return result; <BR>} </P>
<P>if (test_major == 0) test_major = result; /* dynamic */ <BR>return 0; <BR>} <BR>&nbsp;</P>
<P>　　在用insmod命令将编译好的模块调入内存时，init_module 函数被调用。在这里，init_module只做了一件事，就是向系统的字符设备表登记了一个字符设备。register_chrdev需要三个参数，参数一是希望获得的设备号，如果是零的话，系统将选择一个没有被占用的设备号返回。参数二是设备文件名，参数三用来登记驱动程序实际执行操作的函数的指针。 </P>
<P>　　如果登记成功，返回设备的主设备号，不成功，返回一个负值。 </P>
<P>void cleanup_module(void) <BR>{ <BR>unregister_chrdev(test_major， "test"); <BR>}&nbsp; </P>
<P>　　在用rmmod卸载模块时，cleanup_module函数被调用，它释放字符设备test在系统字符设备表中占有的表项。 </P>
<P>　　一个极其简单的字符设备可以说写好了，文件名就叫test.c吧。 </P>
<P>　　下面编译 </P>
<P>　　$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c </P>
<P>　　得到文件test.o就是一个设备驱动程序。 </P>
<P>　　如果设备驱动程序有多个文件，把每个文件按上面的命令行编译，然后 </P>
<P>　　ld -r file1.o file2.o -o modulename. </P>
<P>　　驱动程序已经编译好了，现在把它安装到系统中去。 </P>
<P>　　$ insmod -f test.o </P>
<P>　　如果安装成功，在/proc/devices文件中就可以看到设备test，并可以看到它的主设备号。<BR>　　要卸载的话，运行 </P>
<P>　　$ rmmod test </P>
<P>　　下一步要创建设备文件。 </P>
<P>　　mknod /dev/test c major minor </P>
<P>　　c 是指字符设备，major是主设备号，就是在/proc/devices里看到的。 </P>
<P>　　用shell命令 </P>
<P>　　$ cat /proc/devices | awk "<A href="file://$2==/%22test/">\\$2==\"test\</A>" {print <A href="file://$1/">\\$1</A>}" </P>
<P>　　就可以获得主设备号，可以把上面的命令行加入你的shell script中去。 </P>
<P>　　minor是从设备号，设置成0就可以了。 </P>
<P>　　我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。 </P>
<P>#include &lt;stdio.h&gt; <BR>#include &lt;sys/types.h&gt; <BR>#include &lt;sys/stat.h&gt; <BR>#include &lt;fcntl.h&gt; </P>
<P>main() <BR>{ <BR>int testdev; <BR>int i; <BR>char buf[10]; </P>
<P>testdev = open("/dev/test"，O_RDWR); </P>
<P>if ( testdev == -1 ) <BR>{ <BR>printf("Cann't open file \n"); <BR>exit(0); <BR>} </P>
<P>read(testdev，buf，10); </P>
<P>for (i = 0; i &lt; 10;i++) <BR>printf("%d\n"，buf[i]); </P>
<P>close(testdev); <BR>} <BR>&nbsp;</P>
<P>　　编译运行，看看是不是打印出全1 ？ </P>
<P>　　以上只是一个简单的演示。真正实用的驱动程序要复杂的多，要处理如中断，DMA，I/O port等问题。这些才是真正的难点。请看下节，实际情况的处理。 </P>
<P>　　如何编写Linux操作系统下的设备驱动程序 </P>
<P>　　三、设备驱动程序中的一些具体问题</P>
<P>　　1. I/O Port. </P>
<P>　　和硬件打交道离不开I/O Port，老的ISA设备经常是占用实际的I/O端口，在linux下，操作系统没有对I/O口屏蔽，也就是说，任何驱动程序都可对任意的I/O口操作，这样就很容易引起混乱。每个驱动程序应该自己避免误用端口。 </P>
<P>　　有两个重要的kernel函数可以保证驱动程序做到这一点。 </P>
<P>　　1）check_region(int io_port， int off_set) </P>
<P>　　这个函数察看系统的I/O表，看是否有别的驱动程序占用某一段I/O口。 </P>
<P>　　参数1：io端口的基地址， </P>
<P>　　参数2：io端口占用的范围。 </P>
<P>　　返回值：0 没有占用， 非0，已经被占用。 </P>
<P>　　2）request_region(int io_port， int off_set，char *devname) </P>
<P>　　如果这段I/O端口没有被占用，在我们的驱动程序中就可以使用它。在使用之前，必须向系统登记，以防止被其他程序占用。登记后，在/proc/ioports文件中可以看到你登记的io口。 </P>
<P>　　参数1：io端口的基地址。 </P>
<P>　　参数2：io端口占用的范围。 </P>
<P>　　参数3：使用这段io地址的设备名。 </P>
<P>　　在对I/O口登记后，就可以放心地用inb()， outb()之类的函来访问了。 </P>
<P>　　在一些pci设备中，I/O端口被映射到一段内存中去，要访问这些端口就相当于访问一段内存。经常性的，我们要获得一块内存的物理地址。在dos环境下，（之所以不说是dos操作系统是因为我认为DOS根本就不是一个操作系统，它实在是太简单，太不安全了）只要用段：偏移就可以了。在window95中，95ddk提供了一个vmm 调用 _MapLinearToPhys，用以把线性地址转化为物理地址。但在Linux中是怎样做的呢？ </P>
<P>　　2.内存操作 </P>
<P>　　在设备驱动程序中动态开辟内存，不是用malloc，而是kmalloc，或者用get_free_pages直接申请页。释放内存用的是kfree，或free_pages. 请注意，kmalloc等函数返回的是物理地址！而malloc等返回的是线性地址！关于kmalloc返回的是物理地址这一点本人有点不太明白：既然从线性地址到物理地址的转换是由386cpu硬件完成的，那样汇编指令的操作数应该是线性地址，驱动程序同样也不能直接使用物理地址而是线性地址。但是事实上kmalloc返回的确实是物理地址，而且也可以直接通过它访问实际的RAM，我想这样可以由两种解释，一种是在核心态禁止分页，但是这好像不太现实；另一种是linux的页目录和页表项设计得正好使得物理地址等同于线性地址。我的想法不知对不对，还请高手指教。 </P>
<P>　　言归正传，要注意kmalloc最大只能开辟128k-16，16个字节是被页描述符结构占用了。kmalloc用法参见khg. </P>
<P>　　内存映射的I/O口，寄存器或者是硬件设备的RAM(如显存)一般占用F0000000以上的地址空间。在驱动程序中不能直接访问，要通过kernel函数vremap获得重新映射以后的地址。 </P>
<P>　　另外，很多硬件需要一块比较大的连续内存用作DMA传送。这块内存需要一直驻留在内存，不能被交换到文件中去。但是kmalloc最多只能开辟128k的内存。 </P>
<P>　　这可以通过牺牲一些系统内存的方法来解决。 </P>
<P>　　具体做法是：比如说你的机器由32M的内存，在lilo.conf的启动参数中加上mem=30M，这样linux就认为你的机器只有30M的内存，剩下的2M内存在vremap之后就可以为DMA所用了。 </P>
<P>　　请记住，用vremap映射后的内存，不用时应用unremap释放，否则会浪费页表。 </P>
<P>　　3.中断处理 </P>
<P>　　同处理I/O端口一样，要使用一个中断，必须先向系统登记。 </P>
<P>int request_irq(unsigned int irq ， </P>
<P>void(*handle)(int，void *，struct pt_regs *)， </P>
<P>unsigned int long flags， </P>
<P>const char *device); </P>
<P>irq: 是要申请的中断。 </P>
<P>handle：中断处理函数指针。 </P>
<P>flags：SA_INTERRUPT 请求一个快速中断，0 正常中断。 </P>
<P>device：设备名。<BR>&nbsp;</P>
<P>　　如果登记成功，返回0，这时在/proc/interrupts文件中可以看你请求的中断。 </P>
<P>　　4.一些常见的问题。 </P>
<P>　　对硬件操作，有时时序很重要。但是如果用C语言写一些低级的硬件操作的话，gcc往往会对你的程序进行优化，这样时序就错掉了。如果用汇编写呢，gcc同样会对汇编代码进行优化，除非你用volatile关键字修饰。最保险的办法是禁止优化。这当然只能对一部分你自己编写的代码。如果对所有的代码都不优化，你会发现驱动程序根本无法装载。这是因为在编译驱动程序时要用到gcc的一些扩展特性，而这些扩展特性必须在加了优化选项之后才能体现出来。</P><img src ="http://www.cnitblog.com/ygb/aggbug/7462.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/ygb/" target="_blank">YGB.Grushy</a> 2006-03-10 15:14 <a href="http://www.cnitblog.com/ygb/articles/7462.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux网络驱动程序编写</title><link>http://www.cnitblog.com/ygb/articles/7461.html</link><dc:creator>YGB.Grushy</dc:creator><author>YGB.Grushy</author><pubDate>Fri, 10 Mar 2006 07:05:00 GMT</pubDate><guid>http://www.cnitblog.com/ygb/articles/7461.html</guid><wfw:comment>http://www.cnitblog.com/ygb/comments/7461.html</wfw:comment><comments>http://www.cnitblog.com/ygb/articles/7461.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/ygb/comments/commentRss/7461.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/ygb/services/trackbacks/7461.html</trackback:ping><description><![CDATA[<P>发信人:&nbsp;Bordi&nbsp;(do&nbsp;it),&nbsp;信区:&nbsp;Linux&nbsp;<BR>标&nbsp;&nbsp;题:&nbsp;Linux驱动程序编写&nbsp;<BR>发信站:&nbsp;网易&nbsp;BBS&nbsp;(Sun&nbsp;Aug&nbsp;&nbsp;8&nbsp;11:38:15&nbsp;1999),&nbsp;转信&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;工作需要写了我们公司一块网卡的Linux驱动程序。经历一个从无到有的过程，&nbsp;<BR>深感技术交流的重要。Linux作为挑战微软垄断的强有力武器，日益受到大家的喜&nbsp;<BR>爱。真希望她能在中国迅速成长。把程序文档贴出来，希望和大家探讨Linux技术&nbsp;<BR>和应用，促进Linux在中国的普及。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;本文可随意转载，但请不要在盈利性出版物上刊登。&nbsp;<BR>------------------&nbsp;Linux操作系统网络驱动程序编写&nbsp;-------------------&nbsp;<BR>------------&nbsp;Contact&nbsp;the&nbsp;author&nbsp;by&nbsp;<A href="mailto:mailto:bordi@bordi.dhs.org">mailto:bordi@bordi.dhs.org</A>&nbsp;------&nbsp;<BR>Linux操作系统网络驱动程序编写&nbsp;<BR>一.Linux系统设备驱动程序概述&nbsp;<BR>&nbsp;&nbsp;1.1&nbsp;Linux设备驱动程序分类&nbsp;<BR>&nbsp;&nbsp;1.2&nbsp;编写驱动程序的一些基本概念&nbsp;<BR>二.Linux系统网络设备驱动程序&nbsp;<BR>&nbsp;&nbsp;2.1&nbsp;网络驱动程序的结构&nbsp;<BR>&nbsp;&nbsp;2.2&nbsp;网络驱动程序的基本方法&nbsp;<BR>&nbsp;&nbsp;2.3&nbsp;网络驱动程序中用到的数据结构&nbsp;<BR>&nbsp;&nbsp;2.4&nbsp;常用的系统支持&nbsp;<BR>三.编写Linux网络驱动程序中可能遇到的问题&nbsp;<BR>&nbsp;&nbsp;3.1&nbsp;中断共享&nbsp;<BR>&nbsp;&nbsp;3.2&nbsp;硬件发送忙时的处理&nbsp;<BR>&nbsp;&nbsp;3.3&nbsp;流量控制(flow&nbsp;control)&nbsp;<BR>&nbsp;&nbsp;3.4&nbsp;调试&nbsp;<BR>四.进一步的阅读&nbsp;<BR>五.杂项&nbsp;<BR>一.Linux系统设备驱动程序概述&nbsp;<BR>1.1&nbsp;Linux设备驱动程序分类&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;Linux设备驱动程序在Linux的内核源代码中占有很大的比例，源代码的长度日&nbsp;<BR>益增加，主要是驱动程序的增加。在Linux内核的不断升级过程中，驱动程序的结构&nbsp;<BR>还是相对稳定。在2.0.xx到2.2.xx的变动里，驱动程序的编写做了一些改变，但是&nbsp;<BR>从2.0.xx的驱动到2.2.xx的移植只需做少量的工作。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;Linux系统的设备分为字符设备(char&nbsp;device)，块设备(block&nbsp;device)和网络&nbsp;<BR>设备(network&nbsp;device)三种。字符设备是指存取时没有缓存的设备。块设备的读写&nbsp;<BR>都有缓存来支持，并且块设备必须能够随机存取(random&nbsp;access)，字符设备则没有&nbsp;<BR>这个要求。典型的字符设备包括鼠标，键盘，串行口等。块设备主要包括硬盘软盘&nbsp;<BR>设备，CD-ROM等。一个文件系统要安装进入操作系统必须在块设备上。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD&nbsp;unix的socket&nbsp;<BR>&nbsp;<BR>机制。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系&nbsp;<BR>统里支持对发送数据和接收数据的缓存，提供流量控制机制，提供对多协议的支持。&nbsp;<BR>1.2&nbsp;编写驱动程序的一些基本概念&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;无论是什么操作系统的驱动程序，都有一些通用的概念。操作系统提供给驱动&nbsp;<BR>程序的支持也大致相同。下面简单介绍一下网络设备驱动程序的一些基本要求。&nbsp;<BR>1.2.1&nbsp;发送和接收&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;这是一个网络设备最基本的功能。一块网卡所做的无非就是收发工作。所以驱&nbsp;<BR>动程序里要告诉系统你的发送函数在哪里，系统在有数据要发送时就会调用你的发&nbsp;<BR>送程序。还有驱动程序由于是直接操纵硬件的，所以网络硬件有数据收到最先能得&nbsp;<BR>到这个数据的也就是驱动程序，它负责把这些原始数据进行必要的处理然后送给系&nbsp;<BR>统。这里，操作系统必须要提供两个机制，一个是找到驱动程序的发送函数，一个&nbsp;<BR>是驱动程序把收到的数据送给系统。&nbsp;<BR>1.2.2&nbsp;中断&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;中断在现代计算机结构中有重要的地位。操作系统必须提供驱动程序响应中断&nbsp;<BR>的能力。一般是把一个中断处理程序注册到系统中去。操作系统在硬件中断发生后&nbsp;<BR>调用驱动程序的处理程序。Linux支持中断的共享，即多个设备共享一个中断。&nbsp;<BR>1.2.3&nbsp;时钟&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;在实现驱动程序时，很多地方会用到时钟。如某些协议里的超时处理，没有中&nbsp;<BR>断机制的硬件的轮询等。操作系统应为驱动程序提供定时机制。一般是在预定的时&nbsp;<BR>间过了以后回调注册的时钟函数。在网络驱动程序中，如果硬件没有中断功能，定&nbsp;<BR>时器可以提供轮询(poll)方式对硬件进行存取。或者是实现某些协议时需要的超时&nbsp;<BR>重传等。&nbsp;<BR>二.Linux系统网络设备驱动程序&nbsp;<BR>2.1&nbsp;网络驱动程序的结构&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;所有的Linux网络驱动程序遵循通用的接口。设计时采用的是面向对象的方法。&nbsp;<BR>一个设备就是一个对象(device&nbsp;结构)，它内部有自己的数据和方法。每一个设备的&nbsp;<BR>方法被调用时的第一个参数都是这个设备对象本身。这样这个方法就可以存取自身&nbsp;<BR>的数据(类似面向对象程序设计时的this引用)。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;一个网络设备最基本的方法有初始化、发送和接收。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-------------------&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;---------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;|deliver&nbsp;packets&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|receive&nbsp;packets&nbsp;queue|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;|(dev_queue_xmit())&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|them(netif_rx())&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-------------------&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;---------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;-------------------------------------------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;|&nbsp;methods&nbsp;and&nbsp;variables(initialize,open,close,hard_xmit,|&nbsp;<BR>&nbsp;&nbsp;&nbsp;|&nbsp;interrupt&nbsp;handler,config,resources,status...)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;-------------------------------------------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-----------------&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;----------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|send&nbsp;to&nbsp;hardware&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|receivce&nbsp;from&nbsp;hardware|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-----------------&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;----------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-----------------------------------------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hardware&nbsp;media&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;-----------------------------------------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;初始化程序完成硬件的初始化、device中变量的初始化和系统资源的申请。发送&nbsp;<BR>程序是在驱动程序的上层协议层有数据要发送时自动调用的。一般驱动程序中不对发&nbsp;<BR>送数据进行缓存，而是直接使用硬件的发送功能把数据发送出去。接收数据一般是通&nbsp;<BR>过硬件中断来通知的。在中断处理程序里，把硬件帧信息填入一个skbuff结构中，然&nbsp;<BR>------------------&nbsp;Linux操作系统网络驱动程序编写&nbsp;-------------------&nbsp;<BR>------------&nbsp;Contact&nbsp;the&nbsp;author&nbsp;by&nbsp;<A href="mailto:mailto:bordi@bordi.dhs.org">mailto:bordi@bordi.dhs.org</A>&nbsp;------&nbsp;<BR>后调用netif_rx()传递给上层处理。&nbsp;<BR>2.2&nbsp;网络驱动程序的基本方法&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;网络设备做为一个对象，提供一些方法供系统访问。正是这些有统一接口的方法，&nbsp;<BR>&nbsp;<BR>掩蔽了硬件的具体细节，让系统对各种网络设备的访问都采用统一的形式，做到硬件&nbsp;<BR>无关性。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;下面解释最基本的方法。&nbsp;<BR>2.2.1&nbsp;初始化(initialize)&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;驱动程序必须有一个初始化方法。在把驱动程序载入系统的时候会调用这个初&nbsp;<BR>始化程序。它做以下几方面的工作。检测设备。在初始化程序里你可以根据硬件的&nbsp;<BR>特征检查硬件是否存在，然后决定是否启动这个驱动程序。配置和初始化硬件。在&nbsp;<BR>初始化程序里你可以完成对硬件资源的配置，比如即插即用的硬件就可以在这个时&nbsp;<BR>候进行配置(Linux内核对PnP功能没有很好的支持，可以在驱动程序里完成这个功&nbsp;<BR>能)。配置或协商好硬件占用的资源以后，就可以向系统申请这些资源。有些资源是&nbsp;<BR>可以和别的设备共享的，如中断。有些是不能共享的，如IO、DMA。接下来你要初始&nbsp;<BR>化device结构中的变量。最后，你可以让硬件正式开始工作。&nbsp;<BR>2.2.2&nbsp;打开(open)&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;open这个方法在网络设备驱动程序里是网络设备被激活的时候被调用(即设备状&nbsp;<BR>态由down--&gt;up)。所以实际上很多在initialize中的工作可以放到这里来做。比如资&nbsp;<BR>源的申请，硬件的激活。如果dev-&gt;open返回非0(error)，则硬件的状态还是down。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;open方法另一个作用是如果驱动程序做为一个模块被装入，则要防止模块卸载时&nbsp;<BR>设备处于打开状态。在open方法里要调用MOD_INC_USE_COUNT宏。&nbsp;<BR>2.2.3&nbsp;关闭(stop)&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;close方法做和open相反的工作。可以释放某些资源以减少系统负担。close是在&nbsp;<BR>设备状态由up转为down时被调用的。另外如果是做为模块装入的驱动程序，close里&nbsp;<BR>应该调用MOD_DEC_USE_COUNT，减少设备被引用的次数，以使驱动程序可以被卸载。&nbsp;<BR>另外close方法必须返回成功(0==success)。&nbsp;<BR>2.2.4&nbsp;发送(hard_start_xmit)&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;所有的网络设备驱动程序都必须有这个发送方法。在系统调用驱动程序的xmit&nbsp;<BR>时，发送的数据放在一个sk_buff结构中。一般的驱动程序把数据传给硬件发出去。&nbsp;<BR>也有一些特殊的设备比如loopback把数据组成一个接收数据再回送给系统，或者&nbsp;<BR>dummy设备直接丢弃数据。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;如果发送成功，hard_start_xmit方法里释放sk_buff，返回0(发送成功)。如果&nbsp;<BR>设备暂时无法处理，比如硬件忙，则返回1。这时如果dev-&gt;tbusy置为非0，则系统&nbsp;<BR>认为硬件忙，要等到dev-&gt;tbusy置0以后才会再次发送。tbusy的置0任务一般由中断&nbsp;<BR>完成。硬件在发送结束后产生中断，这时可以把tbusy置0，然后用mark_bh()调用通&nbsp;<BR>知系统可以再次发送。在发送不成功的情况下，也可以不置dev-&gt;tbusy为非0，这样&nbsp;<BR>系统会不断尝试重发。如果hard_start_xmit发送不成功，则不要释放sk_buff。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;传送下来的sk_buff中的数据已经包含硬件需要的帧头。所以在发送方法里不需&nbsp;<BR>要再填充硬件帧头，数据可以直接提交给硬件发送。sk_buff是被锁住的(locked)，&nbsp;<BR>确保其他程序不会存取它。&nbsp;<BR>2.2.5&nbsp;接收(reception)&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;驱动程序并不存在一个接收方法。有数据收到应该是驱动程序来通知系统的。&nbsp;<BR>一般设备收到数据后都会产生一个中断，在中断处理程序中驱动程序申请一块&nbsp;<BR>sk_buff(skb)，从硬件读出数据放置到申请好的缓冲区里。接下来填充sk_buff中&nbsp;<BR>的一些信息。skb-&gt;dev&nbsp;=&nbsp;dev，判断收到帧的协议类型，填入skb-&gt;protocol(多协&nbsp;<BR>议的支持)。把指针skb-&gt;mac.raw指向硬件数据然后丢弃硬件帧头(skb_pull)。还要&nbsp;<BR>设置skb-&gt;pkt_type，标明第二层(链路层)数据类型。可以是以下类型：&nbsp;<BR>&nbsp;&nbsp;PACKET_BROADCAST&nbsp;:&nbsp;链路层广播&nbsp;<BR>&nbsp;&nbsp;PACKET_MULTICAST&nbsp;:&nbsp;链路层组播&nbsp;<BR>&nbsp;&nbsp;PACKET_SELF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;发给自己的帧&nbsp;<BR>&nbsp;&nbsp;PACKET_OTHERHOST&nbsp;:&nbsp;发给别人的帧(监听模式时会有这种帧)&nbsp;<BR>最后调用netif_rx()把数据传送给协议层。netif_rx()里数据放入处理队列然后返&nbsp;<BR>回，真正的处理是在中断返回以后，这样可以减少中断时间。调用netif_rx()以后，&nbsp;<BR>驱动程序就不能再存取数据缓冲区skb。&nbsp;<BR>2.2.6&nbsp;硬件帧头(hard_header)&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;硬件一般都会在上层数据发送之前加上自己的硬件帧头，比如以太网(Ethernet)&nbsp;<BR>就有14字节的帧头。这个帧头是加在上层ip、ipx等数据包的前面的。驱动程序提供&nbsp;<BR>一个hard_header方法，协议层(ip、ipx、arp等)在发送数据之前会调用这段程序。&nbsp;<BR>硬件帧头的长度必须填在dev-&gt;hard_header_len，这样协议层回在数据之前保留好&nbsp;<BR>硬件帧头的空间。这样hard_header程序只要调用skb_push然后正确填入硬件帧头就&nbsp;<BR>可以了。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;在协议层调用hard_header时，传送的参数包括(2.0.xx)：数据的sk_buff，&nbsp;<BR>device指针，protocol，目的地址(daddr)，源地址(saddr)，数据长度(len)。数据&nbsp;<BR>长度不要使用sk_buff中的参数，因为调用hard_header时数据可能还没完全组织好。&nbsp;<BR>saddr是NULL的话是使用缺省地址(default)。daddr是NULL表明协议层不知道硬件目&nbsp;<BR>的地址。如果hard_header完全填好了硬件帧头，则返回添加的字节数。如果硬件帧&nbsp;<BR>头中的信息还不完全(比如daddr为NULL，但是帧头中需要目的硬件地址。典型的情&nbsp;<BR>况是以太网需要地址解析(arp))，则返回负字节数。hard_header返回负数的情况&nbsp;<BR>下，协议层会做进一步的build&nbsp;header的工作。目前Linux系统里就是做arp&nbsp;<BR>(如果hard_header返回正，dev-&gt;arp=1，表明不需要做arp，返回负，dev-&gt;arp=0，&nbsp;<BR>做arp)。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;对hard_header的调用在每个协议层的处理程序里。如ip_output。&nbsp;<BR>2.2.7&nbsp;地址解析(xarp)&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;有些网络有硬件地址(比如Ethernet)，并且在发送硬件帧时需要知道目的硬件&nbsp;<BR>地址。这样就需要上层协议地址(ip、ipx)和硬件地址的对应。这个对应是通过地址&nbsp;<BR>解析完成的。需要做arp的的设备在发送之前会调用驱动程序的rebuild_header方&nbsp;<BR>法。调用的主要参数包括指向硬件帧头的指针，协议层地址。如果驱动程序能够解&nbsp;<BR>析硬件地址，就返回1，如果不能，返回0。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;对rebuild_header的调用在net/core/dev.c的do_dev_queue_xmit()里。&nbsp;<BR>2.2.8&nbsp;参数设置和统计数据&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;在驱动程序里还提供一些方法供系统对设备的参数进行设置和读取信息。一般&nbsp;<BR>只有超级用户(root)权限才能对设备参数进行设置。设置方法有：&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;dev-&gt;set_mac_address()&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;当用户调用ioctl类型为SIOCSIFHWADDR时是要设置这个设备的mac地址。一般&nbsp;<BR>对mac地址的设置没有太大意义的。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;dev-&gt;set_config()&nbsp;<BR>------------------&nbsp;Linux操作系统网络驱动程序编写&nbsp;-------------------&nbsp;<BR>------------&nbsp;Contact&nbsp;the&nbsp;author&nbsp;by&nbsp;<A href="mailto:mailto:bordi@bordi.dhs.org">mailto:bordi@bordi.dhs.org</A>&nbsp;------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;当用户调用ioctl时类型为SIOCSIFMAP时，系统会调用驱动程序的set_config&nbsp;<BR>方法。用户会传递一个ifmap结构包含需要的I/O、中断等参数。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;dev-&gt;do_ioctl()&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;如果用户调用ioctl时类型在SIOCDEVPRIVATE和SIOCDEVPRIVATE+15之间，系统&nbsp;<BR>会调用驱动程序的这个方法。一般是设置设备的专用数据。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;读取信息也是通过ioctl调用进行。除次之外驱动程序还可以提供一个&nbsp;<BR>dev-&gt;get_stats方法，返回一个enet_statistics结构，包含发送接收的统计信息。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;ioctl的处理在net/core/dev.c的dev_ioctl()和dev_ifsioc()里。&nbsp;<BR>2.3&nbsp;网络驱动程序中用到的数据结构&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;最重要的是网络设备的数据结构。定义在include/linux/netdevice.h里。它&nbsp;<BR>的注释已经足够详尽。&nbsp;<BR>struct&nbsp;device&nbsp;<BR>{&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;<BR>&nbsp;&nbsp;&nbsp;*&nbsp;This&nbsp;is&nbsp;the&nbsp;first&nbsp;field&nbsp;of&nbsp;the&nbsp;"visible"&nbsp;part&nbsp;of&nbsp;this&nbsp;structure&nbsp;<BR>&nbsp;&nbsp;&nbsp;*&nbsp;(i.e.&nbsp;as&nbsp;seen&nbsp;by&nbsp;users&nbsp;in&nbsp;the&nbsp;"Space.c"&nbsp;file).&nbsp;&nbsp;It&nbsp;is&nbsp;the&nbsp;name&nbsp;<BR>&nbsp;&nbsp;&nbsp;*&nbsp;the&nbsp;interface.&nbsp;<BR>&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*name;&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;I/O&nbsp;specific&nbsp;fields&nbsp;-&nbsp;FIXME:&nbsp;Merge&nbsp;these&nbsp;and&nbsp;struct&nbsp;ifmap&nbsp;into&nbsp;one&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rmem_end;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;shmem&nbsp;"recv"&nbsp;end&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rmem_start;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;shmem&nbsp;"recv"&nbsp;start&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mem_end;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;shared&nbsp;mem&nbsp;end&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mem_start;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;shared&nbsp;mem&nbsp;start&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;base_addr;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;device&nbsp;I/O&nbsp;address&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;irq;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;device&nbsp;IRQ&nbsp;number&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;Low-level&nbsp;status&nbsp;flags.&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;volatile&nbsp;unsigned&nbsp;char&nbsp;&nbsp;start,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;start&nbsp;an&nbsp;operation&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;interrupt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;interrupt&nbsp;arrived&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;在处理中断时interrupt设为1，处理完清0。&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tbusy;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;transmitter&nbsp;busy&nbsp;must&nbsp;be&nbsp;&nbsp;<BR>long&nbsp;<BR>for&nbsp;<BR>bitops&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;struct&nbsp;device&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*next;&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;The&nbsp;device&nbsp;initialization&nbsp;function.&nbsp;Called&nbsp;only&nbsp;once.&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;指向驱动程序的初始化方法。&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(*init)(struct&nbsp;device&nbsp;*dev);&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;Some&nbsp;hardware&nbsp;also&nbsp;needs&nbsp;these&nbsp;fields,&nbsp;but&nbsp;they&nbsp;are&nbsp;not&nbsp;part&nbsp;of&nbsp;the&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;usual&nbsp;set&nbsp;specified&nbsp;in&nbsp;Space.c.&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;一些硬件可以在一块板上支持多个接口，可能用到if_port。&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if_port;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;Selectable&nbsp;AUI,&nbsp;TP,..*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dma;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;DMA&nbsp;channel&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;struct&nbsp;enet_statistics*&nbsp;(*get_stats)(struct&nbsp;device&nbsp;*dev);&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;<BR>&nbsp;&nbsp;&nbsp;*&nbsp;This&nbsp;marks&nbsp;the&nbsp;end&nbsp;of&nbsp;the&nbsp;"visible"&nbsp;part&nbsp;of&nbsp;the&nbsp;structure.&nbsp;All&nbsp;<BR>&nbsp;&nbsp;&nbsp;*&nbsp;fields&nbsp;hereafter&nbsp;are&nbsp;internal&nbsp;to&nbsp;the&nbsp;system,&nbsp;and&nbsp;may&nbsp;change&nbsp;at&nbsp;<BR>&nbsp;&nbsp;&nbsp;*&nbsp;will&nbsp;(read:&nbsp;may&nbsp;be&nbsp;cleaned&nbsp;up&nbsp;at&nbsp;will).&nbsp;<BR>&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;These&nbsp;may&nbsp;be&nbsp;needed&nbsp;for&nbsp;future&nbsp;network-power-down&nbsp;code.&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;trans_start记录最后一次成功发送的时间。可以用来确定硬件是否工作正常。*/&nbsp;<BR>&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;trans_start;&nbsp;&nbsp;/*&nbsp;Time&nbsp;(in&nbsp;jiffies)&nbsp;of&nbsp;last&nbsp;Tx&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;last_rx;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;Time&nbsp;of&nbsp;last&nbsp;Rx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;flags里面有很多内容，定义在include/linux/if.h里。*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;short&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;flags;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;interface&nbsp;flags&nbsp;(a&nbsp;la&nbsp;BSD)&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;short&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;family;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;address&nbsp;family&nbsp;ID&nbsp;(AF_INET)&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;short&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;metric;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;routing&nbsp;metric&nbsp;(not&nbsp;used)&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;short&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mtu;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;interface&nbsp;MTU&nbsp;value&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;type标明物理硬件的类型。主要说明硬件是否需要arp。定义在&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;include/linux/if_arp.h里。&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;short&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;type;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;interface&nbsp;hardware&nbsp;type&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;上层协议层根据hard_header_len在发送数据缓冲区前面预留硬件帧头空间。*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;short&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hard_header_len;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;hardware&nbsp;hdr&nbsp;length&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;priv指向驱动程序自己定义的一些参数。*/&nbsp;<BR>&nbsp;&nbsp;void&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*priv;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;pointer&nbsp;to&nbsp;private&nbsp;data&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;Interface&nbsp;address&nbsp;info.&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;broadcast[MAX_ADDR_LEN];&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;hw&nbsp;bcast&nbsp;add&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pad;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;make&nbsp;dev_addr&nbsp;ali&nbsp;<BR>gned&nbsp;<BR>to&nbsp;8&nbsp;<BR>bytes&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dev_addr[MAX_ADDR_LEN];&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;hw&nbsp;address&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;addr_len;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;hardware&nbsp;address&nbsp;length&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pa_addr;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;protocol&nbsp;address&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pa_brdaddr;&nbsp;&nbsp;&nbsp;/*&nbsp;protocol&nbsp;broadcast&nbsp;addr&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pa_dstaddr;&nbsp;&nbsp;&nbsp;/*&nbsp;protocol&nbsp;P-P&nbsp;other&nbsp;side&nbsp;addr&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pa_mask;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;protocol&nbsp;netmask&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;short&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pa_alen;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;protocol&nbsp;address&nbsp;length&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;struct&nbsp;dev_mc_list&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*mc_list;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;Multicast&nbsp;mac&nbsp;addresses&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mc_count;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;Number&nbsp;of&nbsp;installed&nbsp;mcasts&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;struct&nbsp;ip_mc_list&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*ip_mc_list;&nbsp;&nbsp;&nbsp;/*&nbsp;IP&nbsp;multicast&nbsp;filter&nbsp;chain&nbsp;&nbsp;&nbsp;&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;__u32&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tx_queue_len;&nbsp;&nbsp;&nbsp;/*&nbsp;Max&nbsp;frames&nbsp;per&nbsp;queue&nbsp;allowed&nbsp;*/&nbsp;<BR>------------------&nbsp;Linux操作系统网络驱动程序编写&nbsp;-------------------&nbsp;<BR>------------&nbsp;Contact&nbsp;the&nbsp;author&nbsp;by&nbsp;<A href="mailto:mailto:bordi@bordi.dhs.org">mailto:bordi@bordi.dhs.org</A>&nbsp;------&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;For&nbsp;load&nbsp;balancing&nbsp;driver&nbsp;pair&nbsp;support&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;unsigned&nbsp;long&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pkt_queue;&nbsp;&nbsp;&nbsp;/*&nbsp;Packets&nbsp;queued&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;struct&nbsp;device&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*slave;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;Slave&nbsp;device&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;struct&nbsp;net_alias_info&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*alias_info;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;main&nbsp;dev&nbsp;alias&nbsp;info&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;struct&nbsp;net_alias&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*my_alias;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;alias&nbsp;devs&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;Pointer&nbsp;to&nbsp;the&nbsp;interface&nbsp;buffers.&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;struct&nbsp;sk_buff_head&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;buffs[DEV_NUMBUFFS];&nbsp;<BR>&nbsp;&nbsp;/*&nbsp;Pointers&nbsp;to&nbsp;interface&nbsp;service&nbsp;routines.&nbsp;*/&nbsp;<BR>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(*open)(struct&nbsp;device&nbsp;*dev);&nbsp;<BR>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(*stop)(struct&nbsp;device&nbsp;*dev);&nbsp;<BR>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(*hard_start_xmit)&nbsp;(struct&nbsp;sk_buff&nbsp;*skb,&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;device&nbsp;*dev);&nbsp;<BR>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(*hard_header)&nbsp;(struct&nbsp;sk_buff&nbsp;*skb,&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;device&nbsp;*dev,&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;short&nbsp;type,&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;*daddr,&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;*saddr,&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;len);&nbsp;<BR>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(*rebuild_header)(void&nbsp;*eth,&nbsp;struct&nbsp;device&nbsp;*dev,&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;long&nbsp;raddr,&nbsp;struct&nbsp;sk_buff&nbsp;*skb);&nbsp;<BR>#define&nbsp;HAVE_MULTICAST&nbsp;<BR>&nbsp;&nbsp;void&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(*set_multicast_list)(struct&nbsp;device&nbsp;*dev);&nbsp;<BR>#define&nbsp;HAVE_SET_MAC_ADDR&nbsp;<BR>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(*set_mac_address)(struct&nbsp;device&nbsp;*dev,&nbsp;void&nbsp;*addr)&nbsp;<BR>;&nbsp;<BR>#define&nbsp;HAVE_PRIVATE_IOCTL&nbsp;<BR>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(*do_ioctl)(struct&nbsp;device&nbsp;*dev,&nbsp;struct&nbsp;ifreq&nbsp;*ifr,&nbsp;<BR>&nbsp;int&nbsp;<BR>cmd);&nbsp;<BR>#define&nbsp;HAVE_SET_CONFIG&nbsp;<BR>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(*set_config)(struct&nbsp;device&nbsp;*dev,&nbsp;struct&nbsp;ifmap&nbsp;*ma&nbsp;<BR>p);&nbsp;<BR>#define&nbsp;HAVE_HEADER_CACHE&nbsp;<BR>&nbsp;&nbsp;void&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(*header_cache_bind)(struct&nbsp;hh_cache&nbsp;**hhp,&nbsp;struct&nbsp;<BR>&nbsp;dev&nbsp;<BR>ce&nbsp;<BR>*dev,&nbsp;unsigned&nbsp;short&nbsp;htype,&nbsp;__u32&nbsp;daddr);&nbsp;<BR>&nbsp;&nbsp;void&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(*header_cache_update)(struct&nbsp;hh_cache&nbsp;*hh,&nbsp;struct&nbsp;<BR>&nbsp;dev&nbsp;<BR>ce&nbsp;<BR>*dev,&nbsp;unsigned&nbsp;char&nbsp;*&nbsp;&nbsp;haddr);&nbsp;<BR>#define&nbsp;HAVE_CHANGE_MTU&nbsp;<BR>&nbsp;&nbsp;int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(*change_mtu)(struct&nbsp;device&nbsp;*dev,&nbsp;int&nbsp;new_mtu);&nbsp;<BR>&nbsp;&nbsp;struct&nbsp;iw_statistics*&nbsp;&nbsp;&nbsp;(*get_wireless_stats)(struct&nbsp;device&nbsp;*dev);&nbsp;<BR>};&nbsp;<BR>2.4&nbsp;常用的系统支持&nbsp;<BR>2.4.1&nbsp;内存申请和释放&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;include/linux/kernel.h里声明了kmalloc()和kfree()。用于在内核模式下申&nbsp;<BR>请和释放内存。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;*kmalloc(unsigned&nbsp;int&nbsp;len,int&nbsp;priority);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;kfree(void&nbsp;*__ptr);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;与用户模式下的malloc()不同，kmalloc()申请空间有大小限制。长度是2的整&nbsp;<BR>次方。可以申请的最大长度也有限制。另外kmalloc()有priority参数，通常使用&nbsp;<BR>时可以为GFP_KERNEL，如果在中断里调用用GFP_ATOMIC参数，因为使用GFP_KERNEL&nbsp;<BR>则调用者可能进入sleep状态，在处理中断时是不允许的。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;kfree()释放的内存必须是kmalloc()申请的。如果知道内存的大小，也可以用&nbsp;<BR>kfree_s()释放。&nbsp;<BR>2.4.2&nbsp;request_irq()、free_irq()&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;这是驱动程序申请中断和释放中断的调用。在include/linux/sched.h里声明。&nbsp;<BR>request_irq()调用的定义：&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;request_irq(unsigned&nbsp;int&nbsp;irq,&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;(*handler)(int&nbsp;irq,&nbsp;void&nbsp;*dev_id,&nbsp;struct&nbsp;pt_regs&nbsp;*regs&nbsp;<BR>),&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;long&nbsp;irqflags,&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;char&nbsp;*&nbsp;devname,&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;*dev_id);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;irq是要申请的硬件中断号。在Intel平台，范围0--15。handler是向系统登记&nbsp;<BR>的中断处理函数。这是一个回调函数，中断发生时，系统调用这个函数，传入的参&nbsp;<BR>数包括硬件中断号，device&nbsp;id，寄存器值。dev_id就是下面的request_irq时传递&nbsp;<BR>给系统的参数dev_id。irqflags是中断处理的一些属性。比较重要的有SA_INTERRUPT，&nbsp;<BR>&nbsp;<BR>标明中断处理程序是快速处理程序(设置SA_INTERRUPT)还是慢速处理程序(不设置&nbsp;<BR>SA_INTERRUPT)。快速处理程序被调用时屏蔽所有中断。慢速处理程序不屏蔽。还有&nbsp;<BR>一个SA_SHIRQ属性，设置了以后运行多个设备共享中断。dev_id在中断共享时会用&nbsp;<BR>到。一般设置为这个设备的device结构本身或者NULL。中断处理程序可以用dev_id&nbsp;<BR>找到相应的控制这个中断的设备，或者用irq2dev_map找到中断对应的设备。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;free_irq(unsigned&nbsp;int&nbsp;irq,void&nbsp;*dev_id);&nbsp;<BR>2.4.3&nbsp;时钟&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;时钟的处理类似中断，也是登记一个时间处理函数，在预定的时间过后，系统&nbsp;<BR>会调用这个函数。在include/linux/timer.h里声明。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;timer_list&nbsp;{&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;timer_list&nbsp;*next;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;struct&nbsp;timer_list&nbsp;*prev;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;long&nbsp;expires;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;long&nbsp;data;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;(*function)(unsigned&nbsp;long);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;};&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;add_timer(struct&nbsp;timer_list&nbsp;*&nbsp;timer);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;del_timer(struct&nbsp;timer_list&nbsp;*&nbsp;timer);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;init_timer(struct&nbsp;timer_list&nbsp;*&nbsp;timer);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;使用时钟，先声明一个timer_list结构，调用init_timer对它进行初始化。&nbsp;<BR>time_list结构里expires是标明这个时钟的周期，单位采用jiffies的单位。&nbsp;<BR>jiffies是Linux一个全局变量，代表时间。它的单位随硬件平台的不同而不同。&nbsp;<BR>系统里定义了一个常数HZ，代表每秒种最小时间间隔的数目。这样jiffies的单位&nbsp;<BR>就是1/HZ。Intel平台jiffies的单位是1/100秒，这就是系统所能分辨的最小时间&nbsp;<BR>间隔了。所以expires/HZ就是以秒为单位的这个时钟的周期。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;function就是时间到了以后的回调函数，它的参数就是timer_list中的data。&nbsp;<BR>data这个参数在初始化时钟的时候赋值，一般赋给它设备的device结构指针。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;在预置时间到系统调用function，同时系统把这个time_list从定时队列里清&nbsp;<BR>除。所以如果需要一直使用定时函数，要在function里再次调用add_timer()把这&nbsp;<BR>------------------&nbsp;Linux操作系统网络驱动程序编写&nbsp;-------------------&nbsp;<BR>------------&nbsp;Contact&nbsp;the&nbsp;author&nbsp;by&nbsp;<A href="mailto:mailto:bordi@bordi.dhs.org">mailto:bordi@bordi.dhs.org</A>&nbsp;------&nbsp;<BR>个timer_list加进定时队列。&nbsp;<BR>2.4.4&nbsp;I/O&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;I/O端口的存取使用：&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;inline&nbsp;unsigned&nbsp;int&nbsp;inb(unsigned&nbsp;short&nbsp;port);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;inline&nbsp;unsigned&nbsp;int&nbsp;inb_p(unsigned&nbsp;short&nbsp;port);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;inline&nbsp;void&nbsp;outb(char&nbsp;value,&nbsp;unsigned&nbsp;short&nbsp;port);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;inline&nbsp;void&nbsp;outb_p(char&nbsp;value,&nbsp;unsigned&nbsp;short&nbsp;port);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;在include/adm/io.h里定义。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;inb_p()、outb_p()与inb()、outb_p()的不同在于前者在存取I/O时有等待&nbsp;<BR>(pause)一适应慢速的I/O设备。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;为了防止存取I/O时发生冲突，Linux提供对端口使用情况的控制。在使用端口&nbsp;<BR>之前，可以检查需要的I/O是否正在被使用，如果没有，则把端口标记为正在使用，&nbsp;<BR>使用完后再释放。系统提供以下几个函数做这些工作。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;check_region(unsigned&nbsp;int&nbsp;from,&nbsp;unsigned&nbsp;int&nbsp;extent);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;request_region(unsigned&nbsp;int&nbsp;from,&nbsp;unsigned&nbsp;int&nbsp;extent,const&nbsp;char&nbsp;*n&nbsp;<BR>ame)&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;release_region(unsigned&nbsp;int&nbsp;from,&nbsp;unsigned&nbsp;int&nbsp;extent);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;其中的参数from表示用到的I/O端口的起始地址，extent标明从from开始的端&nbsp;<BR>口数目。name为设备名称。&nbsp;<BR>2.4.5&nbsp;中断打开关闭&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;系统提供给驱动程序开放和关闭响应中断的能力。是在include/asm/system.h&nbsp;<BR>中的两个定义。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;cli()&nbsp;__asm__&nbsp;__volatile__&nbsp;("cli"::)&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;sti()&nbsp;__asm__&nbsp;__volatile__&nbsp;("sti"::)&nbsp;<BR>2.4.6&nbsp;打印信息&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;类似普通程序里的printf()，驱动程序要输出信息使用printk()。在include&nbsp;<BR>/linux/kernel.h里声明。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;printk(const&nbsp;char*&nbsp;fmt,&nbsp;...);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;其中fmt是格式化字符串。...是参数。都是和printf()格式一样的。&nbsp;<BR>2.4.7&nbsp;注册驱动程序&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;如果使用模块(module)方式加载驱动程序，需要在模块初始化时把设备注册&nbsp;<BR>到系统设备表里去。不再使用时，把设备从系统中卸除。定义在drivers/net/net_init&nbsp;<BR>.h&nbsp;<BR>里的两个函数完成这个工作。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;register_netdev(struct&nbsp;device&nbsp;*dev);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;unregister_netdev(struct&nbsp;device&nbsp;*dev);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;dev就是要注册进系统的设备结构指针。在register_netdev()时，dev结构一&nbsp;<BR>般填写前面11项，即到init，后面的暂时可以不用初始化。最重要的是name指针和&nbsp;<BR>init方法。name指针空(NULL)或者内容为'\0'或者name[0]为空格(space)，则系统&nbsp;<BR>把你的设备做为以太网设备处理。以太网设备有统一的命名格式，ethX。对以太网&nbsp;<BR>这么特别对待大概和Linux的历史有关。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;init方法一定要提供，register_netdev()会调用这个方法让你对硬件检测和&nbsp;<BR>设置。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;register_netdev()返回0表示成功，非0不成功。&nbsp;<BR>2.4.8&nbsp;sk_buff&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;Linux网络各层之间的数据传送都是通过sk_buff。sk_buff提供一套管理缓冲&nbsp;<BR>区的方法，是Linux系统网络高效运行的关键。每个sk_buff包括一些控制方法和一&nbsp;<BR>块数据缓冲区。控制方法按功能分为两种类型。一种是控制整个buffer链的方法，&nbsp;<BR>另一种是控制数据缓冲区的方法。sk_buff组织成双向链表的形式，根据网络应用&nbsp;<BR>的特点，对链表的操作主要是删除链表头的元素和添加到链表尾。sk_buff的控制&nbsp;<BR>方法都很短小以尽量减少系统负荷。(translated&nbsp;from&nbsp;article&nbsp;written&nbsp;by&nbsp;Alan&nbsp;<BR>Cox)&nbsp;<BR>常用的方法包括：&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;.alloc_skb()&nbsp;申请一个sk_buff并对它初始化。返回就是申请到的sk_buff。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;.dev_alloc_skb()类似alloc_skb，在申请好缓冲区后，保留16字节的帧头空&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;间。主要用在Ethernet驱动程序。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;.kfree_skb()&nbsp;释放一个sk_buff。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;.skb_clone()&nbsp;复制一个sk_buff，但不复制数据部分。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;.skb_copy()完全复制一个sk_buff。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;.skb_dequeue()&nbsp;从一个sk_buff链表里取出第一个元素。返回取出的sk_buff，&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如果链表空则返回NULL。这是常用的一个操作。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;.skb_queue_head()&nbsp;在一个sk_buff链表头放入一个元素。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;.skb_queue_tail()&nbsp;在一个sk_buff链表尾放入一个元素。这也是常用的一个&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;操作。网络数据的处理主要是对一个先进先出队列的管理，skb_queue_tail()&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;和skb_dequeue()完成这个工作。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;.skb_insert()&nbsp;在链表的某个元素前插入一个元素。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;.skb_append()&nbsp;在链表的某个元素后插入一个元素。一些协议(如TCP)对没按&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;顺序到达的数据进行重组时用到skb_insert()和skb_append()。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;.skb_reserve()&nbsp;在一个申请好的sk_buff的缓冲区里保留一块空间。这个空间&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;一般是用做下一层协议的头空间的。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;.skb_put()&nbsp;在一个申请好的sk_buff的缓冲区里为数据保留一块空间。在&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;alloc_skb以后，申请到的sk_buff的缓冲区都是处于空(free)状态，有一个&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tail指针指向free空间，实际上开始时tail就指向缓冲区头。skb_reserve()&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在free空间里申请协议头空间，skb_put()申请数据空间。见下面的图。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;.skb_push()&nbsp;把sk_buff缓冲区里数据空间往前移。即把Head&nbsp;room中的空间移&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;一部分到Data&nbsp;area。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;.skb_pull()&nbsp;把sk_buff缓冲区里Data&nbsp;area中的空间移一部分到Head&nbsp;room中。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--------------------------------------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Tail&nbsp;room(free)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--------------------------------------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;After&nbsp;alloc_skb()&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--------------------------------------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;Head&nbsp;room&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Tail&nbsp;room(free)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--------------------------------------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;After&nbsp;skb_reserve()&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--------------------------------------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;Head&nbsp;room&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Data&nbsp;area&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;Tail&nbsp;room(free)&nbsp;&nbsp;|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--------------------------------------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;After&nbsp;skb_put()&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--------------------------------------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|Head|&nbsp;skb_&nbsp;|&nbsp;&nbsp;&nbsp;Data&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;Tail&nbsp;room(free)&nbsp;&nbsp;|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|room|&nbsp;push&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Data&nbsp;area&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--------------------------------------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;After&nbsp;skb_push()&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--------------------------------------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;Head&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;skb_&nbsp;|&nbsp;&nbsp;Data&nbsp;area&nbsp;|&nbsp;Tail&nbsp;room(free)&nbsp;&nbsp;|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;pull&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;Head&nbsp;room&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--------------------------------------------------&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;After&nbsp;skb_pull()&nbsp;<BR>------------------&nbsp;Linux操作系统网络驱动程序编写&nbsp;-------------------&nbsp;<BR>------------&nbsp;Contact&nbsp;the&nbsp;author&nbsp;by&nbsp;<A href="mailto:mailto:bordi@bordi.dhs.org">mailto:bordi@bordi.dhs.org</A>&nbsp;------&nbsp;<BR>三.编写Linux网络驱动程序中需要注意的问题&nbsp;<BR>3.1&nbsp;中断共享&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;Linux系统运行几个设备共享同一个中断。需要共享的话，在申请的时候指明&nbsp;<BR>共享方式。系统提供的request_irq()调用的定义：&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;request_irq(unsigned&nbsp;int&nbsp;irq,&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;(*handler)(int&nbsp;irq,&nbsp;void&nbsp;*dev_id,&nbsp;struct&nbsp;pt_regs&nbsp;*regs&nbsp;<BR>),&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unsigned&nbsp;long&nbsp;irqflags,&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;char&nbsp;*&nbsp;devname,&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;*dev_id);&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;如果共享中断，irqflags设置SA_SHIRQ属性，这样就允许别的设备申请同一个&nbsp;<BR>中断。需要注意所有用到这个中断的设备在调用request_irq()都必须设置这个属&nbsp;<BR>性。系统在回调每个中断处理程序时，可以用dev_id这个参数找到相应的设备。一&nbsp;<BR>般dev_id就设为device结构本身。系统处理共享中断是用各自的dev_id参数依次调&nbsp;<BR>用每一个中断处理程序。&nbsp;<BR>3.2&nbsp;硬件发送忙时的处理&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;主CPU的处理能力一般比网络发送要快，所以经常会遇到系统有数据要发，但&nbsp;<BR>上一包数据网络设备还没发送完。因为在Linux里网络设备驱动程序一般不做数据&nbsp;<BR>缓存，不能发送的数据都是通知系统发送不成功，所以必须要有一个机制在硬件不&nbsp;<BR>忙时及时通知系统接着发送下面的数据。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;一般对发送忙的处理在前面设备的发送方法(hard_start_xmit)里已经描述过，&nbsp;<BR>即如果发送忙，置tbusy为1。处理完发送数据后，在发送结束中断里清tbusy，同&nbsp;<BR>时用mark_bh()调用通知系统继续发送。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;但在具体实现我的驱动程序时发现，这样的处理系统好象并不能及时地知道硬&nbsp;<BR>件已经空闲了，即在mark_bh()以后，系统要等一段时间才会接着发送。造成发送&nbsp;<BR>效率很低。2M线路只有10%不到的使用率。内核版本为2.0.35。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;我最后的实现是不把tbusy置1，让系统始终认为硬件空闲，但是报告发送不成&nbsp;<BR>功。系统会一直尝试重发。这样处理就运行正常了。但是遍循内核源码中的网络驱&nbsp;<BR>动程序，似乎没有这样处理的。不知道症结在哪里。&nbsp;<BR>3.3&nbsp;流量控制(flow&nbsp;control)&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;网络数据的发送和接收都需要流量控制。这些控制是在系统里实现的，不需要&nbsp;<BR>驱动程序做工作。每个设备数据结构里都有一个参数dev-&gt;tx_queue_len，这个参数&nbsp;<BR>标明发送时最多缓存的数据包。在Linux系统里以太网设备(10/100Mbps)&nbsp;<BR>tx_queue_len一般设置为100，串行线路(异步串口)为10。实际上如果看源码可以&nbsp;<BR>知道，设置了dev-&gt;tx_queue_len并不是为缓存这些数据申请了空间。这个参数只是&nbsp;<BR>在收到协议层的数据包时判断发送队列里的数据是不是到了tx_queue_len的限度，&nbsp;<BR>以决定这一包数据加不加进发送队列。发送时另一个方面的流控是更高层协议的发&nbsp;<BR>送窗口(TCP协议里就有发送窗口)。达到了窗口大小，高层协议就不会再发送数据。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;接收流控也分两个层次。netif_rx()缓存的数据包有限制。另外高层协议也会&nbsp;<BR>有一个最大的等待处理的数据量。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;发送和接收流控处理在net/core/dev.c的do_dev_queue_xmit()和netif_rx()&nbsp;<BR>中。&nbsp;<BR>3.4&nbsp;调试&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;很多Linux的驱动程序都是编译进内核的，形成一个大的内核文件。但对调试&nbsp;<BR>来说，这是相当麻烦的。调试驱动程序可以用module方式加载。支持模块方式的&nbsp;<BR>驱动程序必须提供两个函数：int&nbsp;init_module(void)和void&nbsp;cleanup_module(void)。&nbsp;<BR>&nbsp;<BR>init_module()在加载此模块时调用，在这个函数里可以register_netdev()注册&nbsp;<BR>设备。init_module()返回0表示成功，返回负表示失败。cleanup_module()在驱动&nbsp;<BR>程序被卸载时调用，清除占用的资源，调用unregister_netdev()。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;模块可以动态地加载、卸载。在2.0.xx版本里，还有kerneld自动加载模块，&nbsp;<BR>但是2.2.xx中已经取消了kerneld。手工加载使用insmod命令，卸载用rmmod命令，&nbsp;<BR>看内核中的模块用lsmod命令。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;编译驱动程序用gcc，主要命令行参数-DKERNEL&nbsp;-DMODULE。并且作为模块加载&nbsp;<BR>的驱动程序，只编译成obj形式(加-c参数)。编译好的目标文件放在/lib/modules&nbsp;<BR>/2.x.xx/misc下，在启动文件里用insmod加载。&nbsp;<BR>四.进一步的阅读&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;Linux程序设计资料可以从网上获得。这就是开放源代码的好处。并且没有什&nbsp;<BR>么“未公开的秘密”。我编写驱动程序时参阅的主要资料包括：&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;Linux内核源代码&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;&lt;The&nbsp;Linux&nbsp;Kernel&nbsp;Hacker's&nbsp;Guide&gt;&gt;&nbsp;by&nbsp;Michael&nbsp;K.&nbsp;Johnson&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;&lt;Linux&nbsp;Kernel&nbsp;Module&nbsp;Programming&nbsp;Guide&gt;&gt;&nbsp;by&nbsp;Ori&nbsp;Pomerantz&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&lt;&lt;Linux下的设备驱动程序&gt;&gt;&nbsp;by&nbsp;olly&nbsp;in&nbsp;BBS水木清华站&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;可以选择一个模板作为开始，内核源代码里有一个网络驱动程序的模板，&nbsp;<BR>drivers/net/skeleton.c。里面包含了驱动程序的基本内容。但这个模板是以以太&nbsp;<BR>网设备为对象的，以太网的处理在Linux系统里有特殊“待遇”，所以如果不是以&nbsp;<BR>太网设备，有些细节上要注意，主要在初始化程序里。&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;最后，多参照别人写的程序，听听其他开发者的经验之谈大概是最有效的帮助&nbsp;<BR>了。&nbsp;<BR></P><img src ="http://www.cnitblog.com/ygb/aggbug/7461.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/ygb/" target="_blank">YGB.Grushy</a> 2006-03-10 15:05 <a href="http://www.cnitblog.com/ygb/articles/7461.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>